package com.school.device.dynamic;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSON;
import com.mhuang.school.starter.minio.core.MinioFrame;
import com.mhuang.school.starter.minio.util.ToolUtil;
import com.netsdk.lib.NetSDKLib;
import com.netsdk.lib.NetSDKLib.LLong;
import com.netsdk.lib.NetSDKLib.NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY;
import com.netsdk.lib.NetSDKLib.NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY;
import com.netsdk.lib.ToolKits;
import com.school.device.util.CaptureUtil;
import com.school.device.util.DataConvertUtil;
import com.school.device.util.DateTimeUtil;
import com.school.device.util.JsonUtil;
import com.school.device.util.MockMultipartFile;
import com.school.device.util.PingUtil;
import com.sun.jna.Pointer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
public class MultiThreadSdk {
	public ConcurrentHashMap<String, DvIntellifHikvisonDevice> ipDevices = new ConcurrentHashMap<>();// ip,设备
	public Map<String, Thread> threadMap = new ConcurrentHashMap<>();// ip,类型
	public static Map<Long, String> loingIps = new ConcurrentHashMap<Long, String>();// loingId,ip

	ThreadPoolExecutor executor = new ThreadPoolExecutor(   50, // 核心线程数
			50, // 最大线程数
			60, // 空闲线程存活时间
			TimeUnit.SECONDS, // 时间单位
			new LinkedBlockingQueue<>(10) // 任务队列
			);

	@SuppressWarnings("unused")
	private class DeviceThread implements Runnable {
		private DisConnect disConnect;
		private HaveReConnect haveReConnect;// 重连接
		private NetSDKLib netsdk;
		private NetSDKLib.NET_DEVICEINFO_Ex m_stDeviceInfo;
		// 登陆句柄
		private com.netsdk.lib.NetSDKLib.LLong m_hLoginHandle;
		private boolean isListening = false;
		private boolean isInit = false;
		private boolean isOpenLog = false;
		private DvIntellifHikvisonDevice device;
		private MinioFrame minioFrame;
		private String pushUrl;
		private final BlockingQueue<Boolean> keepAlive = new LinkedBlockingQueue<>();
		private volatile boolean running = true;

		public DeviceThread(DvIntellifHikvisonDevice device, MinioFrame minioFrame, String pushUrl) {
			this.disConnect = new DisConnect();
			this.haveReConnect = new HaveReConnect();// 重连接
			this.netsdk = NetSDKLib.NETSDK_INSTANCE;
			this.m_stDeviceInfo = new NetSDKLib.NET_DEVICEINFO_Ex();
			// 登陆句柄
			this.m_hLoginHandle = new com.netsdk.lib.NetSDKLib.LLong(0);
			this.device = device;
			this.minioFrame = minioFrame;
			this.pushUrl = pushUrl;
		}

		public boolean init(NetSDKLib.fDisConnect disConnect, NetSDKLib.fHaveReConnect haveReConnect) {
			isInit = netsdk.CLIENT_Init(disConnect, null);
			if (!isInit) {
				System.out.println("Initialize SDK failed");
				log.info("Initialize SDK failed");
				return false;
			}
			// 打开日志，可选
			NetSDKLib.LOG_SET_PRINT_INFO setLog = new NetSDKLib.LOG_SET_PRINT_INFO();
			File path = new File("./sdklog/");
			if (!path.exists()) {
				path.mkdir();
			}
			String logPath = path.getAbsoluteFile().getParent() + "\\sdklog\\" + ToolKits.getDate() + ".log";
			setLog.nPrintStrategy = 0;
			setLog.bSetFilePath = 1;
			System.arraycopy(logPath.getBytes(), 0, setLog.szLogFilePath, 0, logPath.getBytes().length);
			setLog.bSetPrintStrategy = 1;
			isOpenLog = netsdk.CLIENT_LogOpen(setLog);
			if (!isOpenLog) {
				System.err.println("Failed to open NetSDK log");
				log.error("Failed to open NetSDK log");
			}
			// 设置断线重连回调接口，设置过断线重连成功回调函数后，当设备出现断线情况，SDK内部会自动进行重连操作
			// 此操作为可选操作，但建议用户进行设置
			netsdk.CLIENT_SetAutoReconnect(haveReConnect, null);
			// 设置登录超时时间和尝试次数，可选
			int waitTime = 5000; // 登录请求响应超时时间设置为5S
			int tryTimes = 1; // 登录时尝试建立链接1次
			netsdk.CLIENT_SetConnectTime(waitTime, tryTimes);
			// 设置更多网络参数，NET_PARAM的nWaittime，nConnectTryNum成员与CLIENT_SetConnectTime
			// 接口设置的登录设备超时时间和尝试次数意义相同,可选
			NetSDKLib.NET_PARAM netParam = new NetSDKLib.NET_PARAM();
			netParam.nConnectTime = 10000; // 登录时尝试建立链接的超时时间
			netParam.nGetConnInfoTime = 3000; // 设置子连接的超时时间
			netParam.nGetDevInfoTime = 3000;// 获取设备信息超时时间，为0默认1000ms
			netsdk.CLIENT_SetNetworkParam(netParam);
			return true;
		}

		public void destory() {
			if (isOpenLog)// 关闭日志
				netsdk.CLIENT_LogClose();
			if (isInit)// 关闭netsdk
				netsdk.CLIENT_Cleanup();
		}

		public boolean login(String ip, int port, String username, String password) {
			NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY pstInParam = new NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY();// 入参
			pstInParam.szIP = ip.getBytes();
			pstInParam.nPort = port;
			pstInParam.szUserName = username.getBytes();
			pstInParam.szPassword = password.getBytes();
			// 出参
			NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY pstOutParam = new NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY();
			pstOutParam.stuDeviceInfo = m_stDeviceInfo;
			m_hLoginHandle = netsdk.CLIENT_LoginWithHighLevelSecurity(pstInParam, pstOutParam);
			if (m_hLoginHandle.longValue() == 0) {
				System.err.printf("Login Device[%s] Port[%d]Failed. %s\n", ip, port, ToolKits.getErrorCodePrint());
				log.error("Login Device[%s] Port[%d]Failed. %s\n", ip, port, ToolKits.getErrorCodePrint());
			} else {
				System.out.println("Login Success [ " + ip + " ]");
				log.info("Login Success [ " + ip + " ]");
			}
			return m_hLoginHandle.longValue() == 0 ? false : true;
		}

		// 智能订阅的回调事件
		private class AnalyzerDataCB implements NetSDKLib.fAnalyzerDataCallBack {
			private BufferedImage gateBufferedImage = null;

			@Override
			public int invoke(LLong lAnalyzerHandle, int dwAlarmType, Pointer pAlarmInfo, Pointer pBuffer, int dwBufSize, Pointer dwUser, int nSequence, Pointer reserved) throws UnsupportedEncodingException {
				System.err.println(DateTimeUtil.getCurrentDateTime() + " ip:" + loingIps.get(lAnalyzerHandle.longValue()) + "  事件编号:" + dwAlarmType);
				log.info(DateTimeUtil.getCurrentDateTime() + " ip:" + loingIps.get(lAnalyzerHandle.longValue()) + "  事件编号:" + dwAlarmType);
				if (lAnalyzerHandle.longValue() == 0 || pAlarmInfo == null)
					return -1;
				String ip = loingIps.get(lAnalyzerHandle.longValue());
				switch (dwAlarmType) {
				case NetSDKLib.EVENT_IVS_HIGH_TOSS_DETECT:// 高空抛物 28D
				case NetSDKLib.EVENT_IVS_VIOLENT_THROW_DETECTION: // 暴力抛物检测 27D
				{
					System.err.println("高 抛: " + DateTimeUtil.getCurrentDateTime() + " / ip:" + loingIps.get(lAnalyzerHandle.longValue()) + "  事件编号:" + dwAlarmType);
					try (ByteArrayInputStream byteArrInput = new ByteArrayInputStream(pBuffer.getByteArray(0, dwBufSize))) {
						if (minioFrame == null) {
							System.err.println("minio is null,file:" + getDate() + ".jpg");
							log.error("minio is null");
						} else {
							String picurl = minioFrame.upload(inputStreamToMultipartFile(byteArrInput, getDate() + ".jpg"), null);
							System.err.println("Capt触发事件IP:" + ip + ", 设备IP:" + ip + ", 图片地址:" + picurl + "\r\n");
							log.info("Capt上传文件地址:" + picurl);
							long timeMillis = System.currentTimeMillis();
							AlarmPushUtil.pushMsg(device, picurl, picurl, pushUrl);
							log.info("高抛推送消息耗时:{}", System.currentTimeMillis() - timeMillis);
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
					System.gc();
					break;
				}
				case NetSDKLib.EVENT_IVS_SMOKING_DETECT:// 烟雾
				case NetSDKLib.EVENT_IVS_SMOKEDETECTION:// 烟雾
				case NetSDKLib.EVENT_IVS_FIREDETECTION:// 烟雾
				case NetSDKLib.EVENT_IVS_FIREWARNING:// 烟雾
				{
					log.info("烟 火: " + DateTimeUtil.getCurrentDateTime() + " / ip:" + loingIps.get(lAnalyzerHandle.longValue()) + "  事件编号:" + dwAlarmType);
					System.err.println("烟 火: " + DateTimeUtil.getCurrentDateTime() + " / ip:" + loingIps.get(lAnalyzerHandle.longValue()) + "  事件编号:" + dwAlarmType);
					long timeMillis = System.currentTimeMillis();
					CaptureUtil.sendPicurl(ip, device, pushUrl, minioFrame);
					log.info("烟火推送消息耗时:{}", System.currentTimeMillis() - timeMillis);
					System.gc();
					break;
				}
				default:
					break;
				}
				return 0;
			}
		}

		public void realLoadPic(String ip, int ChannelId, NetSDKLib.fAnalyzerDataCallBack m_AnalyzerDataCB) {
			// 说明： 通道数可以在有登录是返回的信息 m_stDeviceInfo.byChanNum 获取 下列仅订阅了0通道的智能事件.
			int bNeedPicture = 1; // 是否需要图片
			LLong m_hAttachHandle = netsdk.CLIENT_RealLoadPictureEx(m_hLoginHandle, ChannelId, NetSDKLib.EVENT_IVS_ALL, bNeedPicture, m_AnalyzerDataCB, null, null);
			if (m_hAttachHandle.longValue() != 0) {
				loingIps.put(new Long(m_hAttachHandle.longValue()), ip);
				System.out.println("CLIENT_RealLoadPictureEx Success  ChannelId : \n" + ChannelId);
				log.info("订阅成功,{} , {}", ip, m_hAttachHandle);
			} else {
				System.err.println("CLIENT_RealLoadPictureEx Failed!" + ToolKits.getErrorCodePrint());
				log.info("订阅失败,{}, {}", ip, ToolKits.getErrorCodePrint());
				return;
			}
			//while (true) {
			//	 // 适当让出CPU时间  或
			//}
			// 替代while(true)的健康检查循环
			while (running) {
				try {
					// 阻塞等待，释放CPU资源
					keepAlive.poll(100, TimeUnit.MILLISECONDS);

				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
					break;
				}
			}

		}

		public void stop() {
			running = false;
			keepAlive.offer(true); // 唤醒阻塞
		}

		@Override
		public void run() {
			init(disConnect, haveReConnect);
			if (device != null) {
				System.out.println("开始注册: ip:" + device.getDeviceIp() + " port:" + device.getDevicePort() + " userName:" + device.getAccount() + " password:" + device.getPassword() + " type:" + device.getDeviceType());
				log.info("开始注册: ip:" + device.getDeviceIp() + " port:" + device.getDevicePort() + " userName:" + device.getAccount() + " password:" + device.getPassword() + " type:" + device.getDeviceType());
				Boolean flag = login(device.getDeviceIp(), Integer.valueOf(device.getDevicePort()), device.getAccount(), device.getPassword());
				ipDevices.put(device.getDeviceIp(), device);
				if (flag) {
					log.info("注册成功,{}", device.getDeviceIp());
					if (device.getDeviceType() == 1 || device.getDeviceType() == 2) // 高抛 火点
						realLoadPic(device.getDeviceIp(), 0, new AnalyzerDataCB());
				}
			}
		}
	}

	public void start(List<DvIntellifHikvisonDevice> devices, MinioFrame minioFrame, String pushUrl, String ipUrl) throws InterruptedException {
		Set<DvIntellifHikvisonDevice> failureDevices = new HashSet<DvIntellifHikvisonDevice>();
		for (DvIntellifHikvisonDevice device : devices) {
			if (PingUtil.ping(device.getDeviceIp())) {
				DeviceThread deviceThread = new DeviceThread(device, minioFrame, pushUrl);
				executor.submit(deviceThread);
				//Thread thread = new Thread(new DeviceThread(device, minioFrame, pushUrl));
				//thread.start();
				Thread.sleep(5000);
				//threadMap.put(device.getDeviceIp(), thread);
			} else {
				failureDevices.add(device);
			}
			ipDevices.put(device.getDeviceIp(), device);
		}
		while (true) {
			while (failureDevices.size() > 0) {
				Iterator<DvIntellifHikvisonDevice> iterator = failureDevices.iterator();
				while (iterator.hasNext()) {
					DvIntellifHikvisonDevice device = iterator.next();
					String ip = device.getDeviceIp();
					if (PingUtil.ping(ip)) {
						executor.submit(new DeviceThread(device, minioFrame, pushUrl));
						//Thread thread = new Thread(new DeviceThread(device, minioFrame, pushUrl));
						//thread.start();
						//threadMap.put(ip, thread);
						iterator.remove(); // 安全地删除元素
					}
					Thread.sleep(5000);
				}
				Thread.sleep(5000);
			}
			Thread.sleep(120000);
			//
			List<DvIntellifHikvisonDevice> reloadDevices = getDevices(ipUrl);
			if (reloadDevices != null) {
				reloadDevices = devices.stream().filter(item->(item.getDeviceType()==1 || item.getDeviceType()==2)).collect(Collectors.toList());
				if (reloadDevices != null)
					for (DvIntellifHikvisonDevice item : reloadDevices) {
						String ip = item.getDeviceIp();
						if (!ipDevices.containsKey(ip)) {
							ipDevices.put(ip, item);
							failureDevices.add(item);
						} else {
							ipDevices.remove(ip);
							ipDevices.put(ip, item);
						}
					}
			}
		}
	}

	private List<DvIntellifHikvisonDevice> getDevices(String deviceIpUrl) {
		if (ToolUtil.isEmpty(deviceIpUrl)) {
			System.err.println("拉取设备端口接口为空");
			log.error("拉取设备端口接口为空");
			return null;
		}
		HttpResponse response = HttpRequest.get(deviceIpUrl).execute();
		if (response.isOk()) {
			String body = response.body();
			if (ToolUtil.isNotEmpty(body)) {
				Map<String, Object> result = JsonUtil.toMap(body);
				if (DataConvertUtil.getObjectStr(result, "respCode").equalsIgnoreCase("10000000"))
					return JsonUtil.toBeans(JSON.toJSONString(result.get("data")), DvIntellifHikvisonDevice.class);
			}
		}
		System.err.println("请检查连接设备端口");
		log.error("请检查连接设备端口");
		return null;
	}

	@SuppressWarnings("deprecation")
	public void destroy() {
		threadMap.values().forEach((item) -> {
			item.stop();
		});
		// 清理缓存
		// ipLoginHanders = new ConcurrentHashMap<>();// ip,登录状态
		ipDevices = new ConcurrentHashMap<>();// ip,设备
		threadMap = new ConcurrentHashMap<>();// ip,类型
		loingIps = new ConcurrentHashMap<>();
	}

	public static void main(String[] args) throws InterruptedException {
	}

	/***************************/
	private class DisConnect implements NetSDKLib.fDisConnect {
		public DisConnect() {
		}

		@Override
		public void invoke(com.netsdk.lib.NetSDKLib.LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser) {
			System.out.printf("Device[%s] Port[%d] DisConnect!\n", pchDVRIP, nDVRPort);
			log.info("Device{%s} Port{%d} DisConnect!\n", pchDVRIP, nDVRPort);
		}
	}

	private class HaveReConnect implements NetSDKLib.fHaveReConnect {
		public HaveReConnect() {
		}

		@Override
		public void invoke(com.netsdk.lib.NetSDKLib.LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser) {
			System.out.printf("ReConnect Device[%s] Port[%d]\n", pchDVRIP, nDVRPort);
			log.info("ReConnect Device{%s} Port{%d}\n", pchDVRIP, nDVRPort);
		}
	}

	private static MultipartFile inputStreamToMultipartFile(InputStream inputStream, String fileName) {
		try {
			return new MockMultipartFile(fileName, fileName, "image/jpeg", inputStream);
		} catch (IOException e) {
			log.error("输入流转文件失败:", e);
		}
		return null;
	}

	public static String getDate() {
		SimpleDateFormat simpleDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
		return simpleDate.format(new java.util.Date()).replace(" ", "_").replace(":", "-");
	}

}
